const express = require('express');
const crypto = require('@wecom/crypto');
const xml2js = require('xml2js');
const { promisify } = require('util');
const ffi = require('ffi-napi');
const ref = require('ref-napi');
const Struct = require('ref-struct-di')(ref);
const path = require('path');
const fs = require('fs');
const { Buffer } = require('buffer');

// 配置信息 - 从企业微信后台获取
const TOKEN = process.env.WECOM_TOKEN || 'ipjSrT9pDDq0eHWN2HsAw5';
const ENCODING_AES_KEY = process.env.WECOM_ENCODING_AES_KEY || 'J7rGrFV99ASDo9fVaiNYsMQkxWOafGTFdWZkIN4YY4L'; 
const CORP_ID = process.env.WECOM_CORP_ID || 'wx9e04d92a62846a6a';
const SECRET = process.env.WECOM_SECRET || 'OQ7zbA7k_VgHXlk_pzgXz_dL-94KhTIlFOwna3brKDc';
const PORT = process.env.PORT || 3000;

// RSA 私钥
const PRI_KEY = `-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCXyWSEDksPGry6
CKy+oFMSPWD3NZA4BqdsOBjd4LUJrH1CD/SZpsj5vuP2rCtROP1S+EJUaJegWnaq
gZxOkE1ny8HX+82FzSrGRWXREAaIEBl57SyX6T/ep+wCuaxvm4jy/drvKrGEIHRs
KKn4FqCKrgnW4c39Q0xfIAEYf/SJ1UivOFAXGrEmllIgEz+p94sl4yLhfI5I84L6
8qghlI7OVSZ6nP1TSN6WPuvIWzHgGWxK0LFJ4H/zJPDNN1AUbeTn1c8AUKjI6seK
MXofK9qLU/8QaILARdLreAHTWcP+Fl7j5tRH82+R+1c3zOdaWpUvae1IVOoTOtpO
3DD+AhibAgMBAAECggEAEidPOG9ghm8bXkvro9BB+d78ag9DxJJLKNQcE+wBoFAQ
b7u/yeFa4mp0Z6FXfEXgH7G7ISRHP4bs8AnuBgQX4I+hNfHoNpX/Wn+cpKCRihQU
L/MapRU2WIIQkJI2BOfUZ8IjvVIHsXaTkLIGWLpqu1tYUupmZrbLULSKXQHvXyKY
1hK2PorA8OBGk8dEcIq0TmQ174NnZr3z5QTzdPIG72FkOHtsXo7spmb6HLhEBGWE
PAQZU9etX3V2t2YcHdJ9+40XDIuLHSMaYImkBEzc5VdJehSwRoybHOjBD7GM2vI5
2r/Drrs4YN5SogM1byMx6mmM36SzS5Z+BGkg4OzUWQKBgQDHMJ7eL/9aa2TygNv+
Up3Yh8xIuG2xMAifLvF9iFPR8nsSRih/nIJ1rR0V0GvMZjciQKmV3off5LcHVrKM
PrNHlwzoUbI2phQ8CQwPImmDl4teQ8IV1eGrgBVJDcCiJuOilyhVtJRpC476gAqP
/MgRqVLUfP7SKzowYB1GBOBiaQKBgQDDE7zKLokqmNDbdAWOC6/qgfvza+t08WtQ
C9bo4LhwCdU5uxEdZ1LgbWHP7N9iiWsfVExJGON/NiqaekDH/vLbb08r4VlPEECj
JtPu4lGcPDxJHX2l10LpNhOq4cOumlfcZo77FXZBKhtQim5nrz8/v9R7QShIh1QT
ZxGHcuZ6YwKBgCVx3mumWjqFNdTgpHqKzWGHJcAF65kDjpteF8eheSBelQQ6bCKR
KXU1teID7D/gdzzZKwghbSmJtV6LzcTPhKRQoPyLXxUm42blzH6L2YoDUw/FbRbt
vh6fVWEGVNAmv2QxRkPWTo/wvFAbAeFPg8zOJ+QKG10nAylU6Cvhfs0BAoGAZn14
MsFAT6D6GlJamMaLJioktT999XLhbqzJvJBUVojT/gDKfShL4p2VRiitNJf+L/ea
HM12uN88guy4HFtWPPO+JM/AnEltrEo6DsfZGLyM790OppLyFJTUwHLc3F86yhEY
DdK4zoPQ1f/lAZSRJm0itZ8GCxUOlyMPkppLfQECgYB6RXrkXQFh1VVE0Z+dbTA0
PalHgUis5aX5Wh+Y6/p1BIuoOGugz4TZr7iUgfRpmZ01YVp+yMRWOwfZyK+AR8/a
1Zhx0su0qGeFSbSpUYJdIHUhAjQTe1fAbGJsEKmr68ITIe4SQafi7M61+cVwRMVq
SMp33dYbnHguezlApy0MbA==
-----END PRIVATE KEY-----`;

// XML 解析器配置
const parseXML = promisify(new xml2js.Parser({ explicitArray: false, trim: true }).parseString);
const builder = new xml2js.Builder({ cdata: true, headless: true });

// FFI 定义 SDK 结构体
const Slice_t = Struct({
  'buf': 'string',
  'len': 'uint32'
});
const MediaData_t = Struct({
  'data': 'string',
  'data_len': 'uint32',
  'outindex_buf': 'string',
  'is_finish': 'int'
});
const WeWorkFinanceSdk_t = ref.types.void;
const WeWorkFinanceSdk_tPtr = ref.refType(WeWorkFinanceSdk_t);
const Slice_tPtr = ref.refType(Slice_t);
const MediaData_tPtr = ref.refType(MediaData_t);

// 错误码解释函数
function getErrorMsg(code) {
  const errors = {
    0: '成功',
    10001: '参数错误',
    10002: '网络错误',
    10003: '数据解析失败',
    10004: '系统失败',
    10005: '密钥错误',
    10006: 'fileid错误',
    10007: '解密失败',
    10008: '会话存档已过期'
  };
  return errors[code] || `未知错误 (${code})`;
}

// FFI 加载 .so 文件
const sdkLibPath = path.join(__dirname, 'libs/libWeWorkFinanceSdk_C.so');
console.log(`加载 SDK 库: ${sdkLibPath}`);

if (!fs.existsSync(sdkLibPath)) {
  console.error(`错误: SDK 库文件不存在: ${sdkLibPath}`);
  process.exit(1);
}

// 定义 uint64 类型
const uint64 = ref.types.uint64;

// 修正 FFI 定义：明确指定 uint64 类型
const sdkLib = ffi.Library(sdkLibPath, {
  'NewSdk': [WeWorkFinanceSdk_tPtr, []],
  'Init': ['int', [WeWorkFinanceSdk_tPtr, 'string', 'string']],
  'GetChatData': ['int', [WeWorkFinanceSdk_tPtr, uint64, 'uint32', 'string', 'string', 'int', Slice_tPtr]],
  'GetMediaData': ['int', [WeWorkFinanceSdk_tPtr, 'string', 'string', 'string', 'string', uint64, MediaData_tPtr]],
  'DecryptData': ['int', ['string', 'string', Slice_tPtr]],
  'GetContentFromSlice': ['string', [Slice_tPtr]],
  'GetData': ['string', [MediaData_tPtr]],
  'GetDataLen': ['uint32', [MediaData_tPtr]],
  'GetOutIndexBuf': ['string', [MediaData_tPtr]],
  'IsMediaDataFinish': ['int', [MediaData_tPtr]],
  'FreeSlice': ['void', [Slice_tPtr]],
  'FreeMediaData': ['void', [MediaData_tPtr]],
  'DestroySdk': ['void', [WeWorkFinanceSdk_tPtr]]
});

// 初始化 SDK
const sdk = sdkLib.NewSdk();
if (sdk.isNull()) {
  console.error('错误: 创建 SDK 实例失败');
  process.exit(1);
}

console.log(`初始化参数: CORP_ID=${CORP_ID}, SECRET=${SECRET.substring(0, 4)}...`);
const initResult = sdkLib.Init(sdk, CORP_ID, SECRET);
console.log(`SDK 初始化结果: ${initResult} - ${getErrorMsg(initResult)}`);

if (initResult !== 0) {
  console.error('错误: SDK 初始化失败');
  process.exit(1);
}

// RSA 密钥验证函数
function validatePrivateKey() {
  try {
    const crypto = require('crypto');
    const sign = crypto.createSign('RSA-SHA256');
    sign.update('test');
    const signature = sign.sign(PRI_KEY, 'base64');
    console.log('RSA 密钥验证成功');
    return true;
  } catch (e) {
    console.error('RSA 密钥验证失败:', e.message);
    return false;
  }
}

// 启动时验证 RSA 密钥
if (!validatePrivateKey()) {
  console.error('错误: RSA 私钥配置错误，请检查格式是否正确');
  process.exit(1);
}

// RSA 解密函数
function rsaDecryptRandomKey(encryptedKey, publicKeyVer) {
  const crypto = require('crypto');
  try {
    // 1. Base64 解码
    const buffer = Buffer.from(encryptedKey, 'base64');
    
    // 2. RSA 解密
    const decrypted = crypto.privateDecrypt(
      {
        key: PRI_KEY,
        padding: crypto.constants.RSA_PKCS1_PADDING
      },
      buffer
    );
    
    return decrypted.toString('utf8');
  } catch (error) {
    console.error(`RSA 解密失败 (公钥版本 ${publicKeyVer}):`, error.message);
    return null;
  }
}

// 消息解密函数 - 使用 SDK 接口
function decryptChatMsg(decryptKey, encryptedMsg) {
  try {
    // 分配内存给解密后的数据
    const decryptedSlicePtr = ref.alloc(Slice_tPtr);
    
    const decryptRet = sdkLib.DecryptData(
      decryptKey, 
      encryptedMsg, 
      decryptedSlicePtr
    );
    
    if (decryptRet !== 0) {
      console.error(`解密消息失败: ${decryptRet} - ${getErrorMsg(decryptRet)}`);
      return null;
    }
    
    // 获取解密后的内容
    const decryptedContent = sdkLib.GetContentFromSlice(decryptedSlicePtr.deref());
    
    // 释放内存
    sdkLib.FreeSlice(decryptedSlicePtr);
    
    return decryptedContent;
  } catch (e) {
    console.error('解密消息时出错:', e);
    return null;
  }
}

const app = express();

// 中间件配置
app.use(express.urlencoded({ extended: true }));
app.use(express.text({ type: 'text/xml' }));

// GET 请求验证 URL 有效性
app.get('/callback', (req, res) => {
  const { msg_signature, timestamp, nonce, echostr } = req.query;
  
  if (!msg_signature || !timestamp || !nonce || !echostr) {
    return res.status(400).send('参数缺失');
  }

  const calculatedSignature = crypto.getSignature(
    TOKEN, 
    timestamp, 
    nonce, 
    echostr
  );

  if (calculatedSignature !== msg_signature) {
    console.error('签名验证失败', {
      接收签名: msg_signature,
      计算签名: calculatedSignature
    });
    return res.status(403).send('签名验证失败');
  }

  const { message } = crypto.decrypt(ENCODING_AES_KEY, echostr);
  res.set('Content-Type', 'text/plain');
  res.send(message);
});

// POST请求处理消息接收
app.post('/callback', async (req, res) => {
  try {
    const { msg_signature, timestamp, nonce } = req.query;
    
    if (!msg_signature || !timestamp || !nonce) {
      return res.status(400).send('参数缺失');
    }

    const xmlData = req.body;
    const result = await parseXML(xmlData);
    const encryptMsg = result.xml.Encrypt;
    
    if (!encryptMsg) {
      return res.status(400).send('无效的消息格式');
    }

    const calculatedSignature = crypto.getSignature(
      TOKEN, 
      timestamp, 
      nonce, 
      encryptMsg
    );

    if (calculatedSignature !== msg_signature) {
      console.error('签名验证失败', {
        接收签名: msg_signature,
        计算签名: calculatedSignature
      });
      return res.status(403).send('签名验证失败');
    }

    const decrypted = crypto.decrypt(ENCODING_AES_KEY, encryptMsg);
    const message = await parseXML(decrypted.message);
    
    console.log('收到消息:', JSON.stringify(message.xml, null, 2));
    
    const response = await handleMessage(message.xml);
    
    if (response) {
      const encryptResponse = crypto.encrypt(ENCODING_AES_KEY, response);
      const newTimestamp = Math.floor(Date.now() / 1000).toString();
      const newNonce = Math.random().toString(36).substring(2, 15);
      
      const responseSignature = crypto.getSignature(
        TOKEN,
        newTimestamp,
        newNonce,
        encryptResponse
      );
      
      const responseXml = builder.buildObject({
        xml: {
          Encrypt: encryptResponse,
          MsgSignature: responseSignature,
          TimeStamp: newTimestamp,
          Nonce: newNonce
        }
      });
      
      res.set('Content-Type', 'application/xml');
      return res.send(responseXml);
    }
    
    res.status(200).end('');
    
  } catch (error) {
    console.error('处理消息时出错:', error);
    res.status(500).send('服务器内部错误');
  }
});

// 会话存档接口 - 修复 uint64 处理问题
app.get('/archive', async (req, res) => {
  try {
    // 1. 解析参数
    const seq = parseInt(0);
    let limit = parseInt(req.query.limit || 10);
    
    // 限制最大拉取数量
    if (limit > 1000) {
      limit = 1000;
      console.log(`限制拉取数量为最大值: 1000`);
    }
    
    const timeout = 10; // 超时时间（秒） - 官方文档要求
    
    console.log(`获取会话存档: seq=${seq}, limit=${limit}, timeout=${timeout}s`);

    // 2. 分配指针内存
    const chatDatasPtr = ref.alloc(Slice_tPtr);
    
    // 3. 创建 uint64 值 - 正确方式
    // 注意: 这里我们创建一个 Buffer 来存储 uint64 值
    const seqBuffer = Buffer.alloc(8); // 8 bytes for uint64
    seqBuffer.writeBigUInt64LE(seq);
    
    // 4. 调用 GetChatData
    const ret = sdkLib.GetChatData(
      sdk,
      BigInt(seq),  
      limit,      // uint32 (数字)
      '',         // proxy
      '',         // passwd
      timeout,    // int (秒)
      chatDatasPtr
    );

    console.log(`GetChatData 返回: ${ret} - ${getErrorMsg(ret)}`);
    
    if (ret !== 0) {
      return res.status(500).json({ 
        error: '获取聊天记录失败',
        code: ret,
        message: getErrorMsg(ret)
      });
    }

    // 5. 获取数据内容
    const chatDatas = chatDatasPtr.deref();
    const content = sdkLib.GetContentFromSlice(chatDatas);
    
    if (!content) {
      console.error('获取到的聊天内容为空');
      sdkLib.FreeSlice(chatDatasPtr);
      return res.status(500).json({ error: '聊天内容为空' });
    }
    
    // 6. 解析聊天数据
    let messages;
    try {
      messages = JSON.parse(content);
    } catch (e) {
      console.error('解析聊天记录失败:', e);
      sdkLib.FreeSlice(chatDatasPtr);
      return res.status(500).json({ error: '解析聊天记录失败' });
    }
    
    // 7. 检查错误码
    if (messages.errcode !== 0) {
      console.error('企业微信返回错误:', {
        errcode: messages.errcode,
        errmsg: messages.errmsg
      });
      sdkLib.FreeSlice(chatDatasPtr);
      return res.status(500).json({
        error: '企业微信返回错误',
        errcode: messages.errcode,
        errmsg: messages.errmsg
      });
    }
    
    console.log(`获取到 ${messages.chatdata?.length || 0} 条聊天记录`);
    
    const decryptedMessages = [];
    
    // 8. 处理每条消息
    if (messages.chatdata && messages.chatdata.length > 0) {
      for (const msg of messages.chatdata) {
        try {
          console.log(`处理消息: seq=${msg.seq}, msgid=${msg.msgid}, publickey_ver=${msg.publickey_ver}`);
          
          // 8.1 RSA 解密随机密钥
          const randomKey = rsaDecryptRandomKey(msg.encrypt_random_key, msg.publickey_ver);
          if (!randomKey) {
            console.error('解密随机密钥失败');
            continue;
          }
          
          // 8.2 解密消息内容
          const decryptedContent = decryptChatMsg(randomKey, msg.encrypt_chat_msg);
          if (!decryptedContent) {
            console.error('解密消息内容失败');
            continue;
          }
          
          // 8.3 解析消息内容
          try {
            const messageObj = JSON.parse(decryptedContent);
            decryptedMessages.push({
              seq: msg.seq,
              msgid: msg.msgid,
              publickey_ver: msg.publickey_ver,
              ...messageObj
            });
          } catch (e) {
            console.error('解析解密后的消息失败:', e);
            decryptedMessages.push({
              seq: msg.seq,
              msgid: msg.msgid,
              error: '解析消息失败',
              raw: decryptedContent
            });
          }
          
        } catch (e) {
          console.error('处理单条消息出错:', e);
        }
      }
    }
    
    // 9. 释放原始数据内存
    sdkLib.FreeSlice(chatDatasPtr);
    
    // 10. 返回结果
    res.json({ 
      errcode: 0,
      errmsg: 'ok',
      next_seq: messages.next_seq ? messages.next_seq.toString() : "0",
      messages: decryptedMessages
    });
    
  } catch (error) {
    console.error('获取会话存档出错:', error);
    res.status(500).json({ 
      errcode: 500,
      errmsg: '服务器内部错误'
    });
  }
});

// SDK 状态检查接口
app.get('/sdk-status', (req, res) => {
  res.json({
    initialized: initResult === 0,
    initResult,
    errorMsg: getErrorMsg(initResult),
    rsaValid: validatePrivateKey(),
    sdkInstance: !sdk.isNull(),
    env: {
      token: TOKEN.substring(0, 4) + '...',
      encodingAESKey: ENCODING_AES_KEY.substring(0, 4) + '...',
      corpId: CORP_ID,
      secret: SECRET.substring(0, 4) + '...'
    }
  });
});

// 消息处理函数
async function handleMessage(message) {
  const msgType = message.MsgType;
  if (msgType === 'text') {
    return buildTextMessage(
      message.FromUserName,
      message.ToUserName,
      `已收到您的消息：${message.Content}`
    );
  }
  if (msgType === 'event') {
    const eventType = message.Event;
    switch (eventType) {
      case 'subscribe':
        return buildTextMessage(
          message.FromUserName,
          message.ToUserName,
          '欢迎关注！发送任意消息开始互动'
        );
      case 'unsubscribe':
        console.log(`用户 ${message.FromUserName} 取消关注`);
        return null;
      case 'click':
        return buildTextMessage(
          message.FromUserName,
          message.ToUserName,
          `您点击了菜单: ${message.EventKey}`
        );
    }
  }
  return buildTextMessage(
    message.FromUserName,
    message.ToUserName,
    `暂不支持${msgType}类型的消息`
  );
}

// 构建文本回复消息
function buildTextMessage(toUser, fromUser, content) {
  return builder.buildObject({
    xml: {
      ToUserName: { _: toUser },
      FromUserName: { _: fromUser },
      CreateTime: Math.floor(Date.now() / 1000),
      MsgType: { _: 'text' },
      Content: { _: content }
    }
  });
}

// 启动服务
app.listen(PORT, () => {
  console.log(`=============================================`);
  console.log(`企业微信回调服务已启动`);
  console.log(`服务地址: http://localhost:${PORT}/callback`);
  console.log(`存档接口: http://localhost:${PORT}/archive?seq=0&limit=100`);
  console.log(`状态检查: http://localhost:${PORT}/sdk-status`);
  console.log(`Token: ${TOKEN}`);
  console.log(`EncodingAESKey: ${ENCODING_AES_KEY}`);
  console.log(`CorpID: ${CORP_ID}`);
  console.log(`Secret: ${SECRET}`);
  console.log('配置说明:');
  console.log('1. 在企业微信后台配置回调URL时使用 /callback');
  console.log('2. 确保填入相同的Token和EncodingAESKey');
  console.log('3. 支持接收文本消息和事件消息');
  console.log('4. 使用 /archive 获取会话存档');
  console.log('5. 使用 /sdk-status 检查服务状态');
  console.log('=============================================');
});

// 清理 SDK
process.on('exit', () => {
  if (sdk && !sdk.isNull()) {
    console.log('清理 SDK 资源...');
    sdkLib.DestroySdk(sdk);
  }
});